package co.edu.unal.bioingenium.kbmed.knowledge.mapping.impl;

import co.edu.unal.bioingenium.kbmed.config.Configuration;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.api.Mapping;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.heuristic.MatchingFunctions;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.vo.ConceptIdentified;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.vo.DescriptorScore;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.OntologyMetaData;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.dao.KnowledgeSourceDAO;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.dao.KnowledgeSourceDAOFactory;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.vo.Concept;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.vo.Descriptor;
import co.edu.unal.bioingenium.kbmed.text.filter.FilterPipe;
import co.edu.unal.bioingenium.kbmed.text.index.inverted.io.InvertedIndexIO;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.vo.ScoresMap;
import co.edu.unal.bioingenium.kbmed.text.index.structures.InvertedIndexStrToWordInContext;
import co.edu.unal.bioingenium.kbmed.text.representation.WordList;
import co.edu.unal.bioingenium.kbmed.text.representation.io.WordListIO;
import co.edu.unal.bioingenium.kbmed.text.util.TextUtil;
import co.edu.unal.bioingenium.kbmed.util.system.Memory;
import ds.tree.RadixTree;
import ds.tree.io.PrefixIndexIO;
import gnu.trove.TShortHashSet;
import gnu.trove.TShortIterator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric;
import uk.ac.shef.wit.simmetrics.similaritymetrics.JaroWinkler;

/**
 *
 * @author Alejandro Riveros Cruz
 */
public class SoftMapping implements Mapping {

    private OntologyMetaData ontologyMetaData;
    private FilterPipe tokenPipe;
    private FilterPipe sentencePipe;
    //Index structures
    private RadixTree<Integer> descriptorsPrefixIndex;
    private InvertedIndexStrToWordInContext wordByDescriptorContextIndex;
    private List<String> sortedWordList;
    //Mapping structures
//    private Map<String, Descriptor> descriptorByDescriptorId;
//    private Map<String, Concept> conceptByConceptId;
    //Mapping structures
    private Map<String, String> conceptIdByDescriptorId;
    private Map<String, String> categoryByConceptId;
    //Constants
    private AbstractStringMetric stringMetric;
    private double STRING_METRIC_THRESHOLD = 0.5;
    private double SIMILARITY_THRESHOLD = 0.85;
    private boolean verbose = false;

    /**
     * @param:
     * @return:
     */
    public SoftMapping(OntologyMetaData ontologyMetaData) {
        this.ontologyMetaData = ontologyMetaData;
        stringMetric = new JaroWinkler();
    }

    /**
     * 
     */
    @Override
    public void init() {
        try {

            KnowledgeSourceDAO knowledgeSourceDAO = KnowledgeSourceDAOFactory.getKnowledgeSourceDAO(ontologyMetaData);
            System.out.print("Load descriptor by descriptorId from knowledge source...");
//            descriptorByDescriptorId = knowledgeSourceDAO.getDescriptorsByDescriptorId(ontologyMetaData.getConceptStatusActive(), ontologyMetaData.getDescriptorStatusActive());
            conceptIdByDescriptorId = knowledgeSourceDAO.getConceptIdByDescriptorId();
            System.out.print("OK\n");
            Memory.showUsage("");
            System.out.print("Load concept by conceptId from knowledge source...");
//            categoryByConceptId = knowledgeSourceDAO.getConceptbyConceptId(ontologyMetaData.getConceptStatusActive());
            categoryByConceptId = knowledgeSourceDAO.getCategoryByConceptId();
            System.out.print("OK\n");
            Memory.showUsage("");
            knowledgeSourceDAO.disconnect();
            knowledgeSourceDAO = null;
            sentencePipe = new FilterPipe();
            sentencePipe.setRemoveNumbers(true);
            tokenPipe = new FilterPipe();
            System.out.print("Load word by descriptor index...");
            wordByDescriptorContextIndex = InvertedIndexIO.loadStrToWordInContextInvertedIndex(Configuration.KNOWLEDGE_SOURCE_NAME + "WordByDescriptorCtx.idx");
            System.out.print("OK\n");
            System.out.print("Load descriptors prefix index...");
            descriptorsPrefixIndex = (RadixTree<Integer>) PrefixIndexIO.load(Configuration.KNOWLEDGE_SOURCE_NAME + "DescriptorsWordsPrefix.idx");
            System.out.print("OK\n");
            System.out.print("Load word list...");
            WordList wordList = WordListIO.loadWordList(Configuration.KNOWLEDGE_SOURCE_NAME + "WordByConcept.lst");
            sortedWordList = wordList.getSortedWordList();
            System.out.print("OK\n");
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(SoftMapping.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(SoftMapping.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * @param:
     * @return:
     * @Override
     */
    @Override
    public Map<String, List<ConceptIdentified>> doMapping(String text) {
        Map<String, List<ConceptIdentified>> results = new HashMap<String, List<ConceptIdentified>>();
        List<String> sentences = TextUtil.getSentences(text);
        for (String sentence : sentences) {
            results.put(sentence, mapSentence(sentence));
        }
        return results;
    }

    /**
     * @param:
     * @return:
     * @Override
     */
    @Override
    public List<ConceptIdentified> mapSentence(String sentence) {
        ScoresMap sentenceScoresMap = buildCandidateDescriptorSet(sentence);
        List<ConceptIdentified> identifiedConcepts = evaluateCandidates(sentenceScoresMap);
        if (verbose) {
            System.out.println("SENTENCE: " + sentence);
            for (ConceptIdentified conceptIdentified : identifiedConcepts) {
                System.out.println(conceptIdentified);
            }
            System.out.println("\n");
        }
        return identifiedConcepts;
    }

    @Override
    public List<ConceptIdentified> evaluateCandidates(ScoresMap scoresMap) {
        Map<BitSet, ConceptIdentified> conceptIdentifiedsMap = new HashMap<BitSet, ConceptIdentified>();
        List<ConceptIdentified> conceptIdentifieds = new ArrayList<ConceptIdentified>();
        String conceptId, category;
        DescriptorScore descriptorScore;
        ConceptIdentified conceptIdentified;
        Map<BitSet, Double> maxMap = new HashMap<BitSet, Double>();
        for (String descriptorId : scoresMap.getMap().keySet()) {
            descriptorScore = scoresMap.getMap().get(descriptorId);
            conceptId = conceptIdByDescriptorId.get(descriptorId);
            category = categoryByConceptId.get(conceptId);
//            descriptorScore.setDescriptorTokens(conceptIdByDescriptorId.get(descriptorId).getNumTokens());
            descriptorScore.setTotalScore(MatchingFunctions.function(descriptorScore, scoresMap.getMaxScores()));
            //Select concepts
            if (descriptorScore.getTotalScore() >= SIMILARITY_THRESHOLD) {
                if (maxMap.containsKey(descriptorScore.getBitSet())) {
                    if (maxMap.get(descriptorScore.getBitSet()) < descriptorScore.getTotalScore()) {
                        maxMap.put(descriptorScore.getBitSet(), descriptorScore.getTotalScore());
                        conceptIdentified = new ConceptIdentified(conceptId, descriptorId, conceptIdByDescriptorId.get(descriptorId), category, descriptorScore.getTotalScore());
                        conceptIdentifiedsMap.put(descriptorScore.getBitSet(), conceptIdentified);
                    }
                } else {
                    maxMap.put(descriptorScore.getBitSet(), descriptorScore.getTotalScore());
                    conceptIdentified = new ConceptIdentified(conceptId, descriptorId, conceptIdByDescriptorId.get(descriptorId), category, descriptorScore.getTotalScore());
                    conceptIdentifiedsMap.put(descriptorScore.getBitSet(), conceptIdentified);
                }
            }
        }
        for (BitSet BitSet : conceptIdentifiedsMap.keySet()) {
            conceptIdentifieds.add(conceptIdentifiedsMap.get(BitSet));
        }
        Collections.sort(conceptIdentifieds);
        return conceptIdentifieds;
    }

    /**
     * @param:
     * @return:
     * @Override
     */
    @Override
    public ScoresMap buildCandidateDescriptorSet(String sentence) {
        List<String> words = tokenPipe.filter(sentence);
        ScoresMap sentenceScoresMap = new ScoresMap(words);
        Map<String, TShortHashSet> contextMap;
        String word = "", prefix = "";
        int chosenIndex, upperBound, lowerBound;
        double similarity;
        for (int wordIdx = 0; wordIdx < words.size(); wordIdx++) {
            try {
                word = words.get(wordIdx);
                if (word != null) {
                    if (word.length() > 4) {
                        prefix = word.substring(0, 4);
                    } else {
                        prefix = word;
                    }
                    chosenIndex = descriptorsPrefixIndex.find(prefix);
                    lowerBound = chosenIndex;
                    upperBound = chosenIndex + 1;
                    while (sortedWordList.get(lowerBound).startsWith(prefix)) {
                        if ((similarity = stringMetric.getSimilarity(word, sortedWordList.get(lowerBound))) >= STRING_METRIC_THRESHOLD) {
                            contextMap = wordByDescriptorContextIndex.getElement(word);
                            for (String key : contextMap.keySet()) {
                                for (TShortIterator it = contextMap.get(key).iterator(); it.hasNext();) {
                                    sentenceScoresMap.setDetailedScore(wordIdx, key, it.next(), similarity);
                                }
                            }
                        }
                        lowerBound--;
                    }
                    while (sortedWordList.get(upperBound).startsWith(prefix)) {
                        if ((similarity = stringMetric.getSimilarity(word, sortedWordList.get(upperBound))) >= STRING_METRIC_THRESHOLD) {
                            contextMap = wordByDescriptorContextIndex.getElement(word);
                            for (String key : contextMap.keySet()) {
                                for (TShortIterator it = contextMap.get(key).iterator(); it.hasNext();) {
                                    sentenceScoresMap.setDetailedScore(wordIdx, key, it.next(), similarity);
                                }
                            }
                        }
                        upperBound++;
                    }
                }
            } catch (NullPointerException ex) {
//                Logger.getLogger(SoftMapping.class.getName()).log(Level.WARNING, "The prefix {0}:{1} is not present in the ontology index", new Object[]{word, prefix});
            }
        }
        return sentenceScoresMap;
    }

    /**
     * 
     * @return
     */
    @Override
    public double getSIMILARITY_THRESHOLD() {
        return SIMILARITY_THRESHOLD;
    }

    /**
     * 
     * @return
     */
    @Override
    public double getSTRING_METRIC_THRESHOLD() {
        return STRING_METRIC_THRESHOLD;
    }

    /**
     * 
     * @param SIMILARITY_THRESHOLD
     */
    @Override
    public void setSIMILARITY_THRESHOLD(double SIMILARITY_THRESHOLD) {
        this.SIMILARITY_THRESHOLD = SIMILARITY_THRESHOLD;
    }

    /**
     * 
     * @param STRING_METRIC_THRESHOLD
     */
    @Override
    public void setSTRING_METRIC_THRESHOLD(double STRING_METRIC_THRESHOLD) {
        this.STRING_METRIC_THRESHOLD = STRING_METRIC_THRESHOLD;
    }

    /**
     * 
     * @return
     */
    @Override
    public AbstractStringMetric getStringMetric() {
        return stringMetric;
    }

    /**
     * 
     * @param verbose
     */
    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }
}
